/*
 Copyright (c) [Year] [name of copyright holder]
 [Software Name] is licensed under Mulan PSL v2.
 You can use this software according to the terms and conditions of the Mulan
 PSL v2.
 You may obtain a copy of Mulan PSL v2 at:
          http://license.coscl.org.cn/MulanPSL2
 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
 KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
 NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 See the Mulan PSL v2 for more details.
*/

#include "PasteDB.hpp"

// 定义一个模板类，用于管理MySQL预处理语句 RAII(源获取即初始化)
class StmtGuard {
  public:
    // StmtGuard类的构造函数，用于初始化MySQL预处理语句对象
    // 参数conn是MySQL连接对象的指针，用于创建预处理语句
    explicit StmtGuard(MYSQL *conn) : stmt(mysql_stmt_init(conn)), conn(conn) {
        // 检查mysql_stmt_init()函数是否成功创建了预处理语句对象
        // 如果创建失败，则抛出运行时错误异常，说明预处理语句初始化失败
        if (!stmt) {
            throw std::runtime_error("mysql_stmt_init() 初始化预处理错误!");
        }
    }

    ~StmtGuard() {
        if (stmt) {
            mysql_stmt_close(stmt);
        }
    }

    /**
     * 获取MYSQL_STMT类型的指针
     *
     * 该函数用于返回一个指向MYSQL_STMT对象的指针，MYSQL_STMT是MySQL预处理语句的基础
     * 使用预处理语句可以提高代码的安全性，防止SQL注入，并且可能提高执行效率
     *
     * @return MYSQL_STMT* 返回指向MYSQL_STMT对象的指针，用于执行预处理语句的操作
     */
    MYSQL_STMT *get() const { return stmt; }

    // 禁止StmtGuard对象的拷贝构造，以防止外部代码直接复制对象实例
    StmtGuard(const StmtGuard &) = delete;
    // 禁止StmtGuard对象的赋值操作
    StmtGuard &operator=(const StmtGuard &) = delete;

    // 允许移动
    // 移动构造函数，用于在资源管理类StmtGuard之间转移资源所有权
    StmtGuard(StmtGuard &&other) noexcept : stmt(other.stmt), conn(other.conn) {
        // 将其他实例的stmt设置为nullptr，表示资源已转移
        other.stmt = nullptr;
    }

    // 移动赋值运算符重载，用于高效地处理资源转移
    StmtGuard &operator=(StmtGuard &&other) noexcept {
        // 检查自赋值情况，避免无效操作
        if (this != &other) {
            // 资源转移：将other的stmt和conn指针赋值给当前对象
            stmt = other.stmt;
            conn = other.conn;
            // 将other的stmt指针置为nullptr，表示资源已转移
            other.stmt = nullptr;
        }
        // 返回当前对象引用，支持链式赋值
        return *this;
    }

  private:
    MYSQL_STMT *stmt;
    MYSQL *conn;
};

// 预处理语句绑定助手类
class BindHelper {
  private:
    MYSQL_STMT *stmt_;
    MYSQL_BIND bind_[32]; // 根据实际需求调整最大字段数
    int bindIndex_;

  public:
    // BindHelper类的构造函数
    // 该构造函数初始化了BindHelper对象，主要用于绑定MySQL预处理语句参数
    // 参数:
    //   stmt: 指向MYSQL_STMT的指针，代表预处理语句
    explicit BindHelper(MYSQL_STMT *stmt) : stmt_(stmt), bindIndex_(0) {
        // 初始化绑定数组
        // 使用memset函数将bind_数组的所有元素设置为0，确保数组在使用前被正确初始化
        memset(bind_, 0, sizeof(bind_));
    }

    /**
     * 向MySQL绑定数组中添加一个字符串类型的绑定项
     *
     * @param buffer 存储字符串数据的缓冲区指针
     * @param indicator 指示变量指针，用于指示数据的长度或错误状态
     * @param bufferSize 缓冲区的大小，(未指定则默认为-1 输入缓冲区时自己计算长度,)绑定查询结果时 作为输出缓冲区必须指定长度
     *
     * 此函数将一个字符串数据绑定到MySQL预处理语句中的一个参数
     * 它通过配置MYSQL_BIND结构体来告诉MySQL驱动如何处理这块内存中的数据
     * 选择使用引用和指针是为了直接操作传入的数据，避免不必要的拷贝
     */
    void AddString(void *buffer, char *indicator, unsigned long bufferSize = -1) {
        // 获取当前绑定索引对应的MYSQL_BIND对象，并增加绑定索引
        MYSQL_BIND &bind = bind_[bindIndex_++];

        // 设置绑定项的数据类型为字符串
        bind.buffer_type = MYSQL_TYPE_STRING;

        // 设置绑定项的数据缓冲区指针
        bind.buffer = buffer;

        // 设置绑定项的缓冲区长度
        bind.buffer_length = bufferSize;

        // 设置绑定项的指示变量指针，用于指示数据长度或错误状态
        bind.u.indicator = indicator;
    }

    // 添加32位整型字段（int）
    void AddInt32(int32_t *value, char *indicator) {
        MYSQL_BIND &bind = bind_[bindIndex_++];
        bind.buffer_type = MYSQL_TYPE_LONG;
        bind.buffer = value;
        bind.u.indicator = indicator;
    }

    // 添加64位长整型字段（long long int）
    void AddInt64(int64_t *value, char *indicator) {
        MYSQL_BIND &bind = bind_[bindIndex_++];
        bind.buffer_type = MYSQL_TYPE_LONGLONG;
        bind.buffer = value;
        bind.u.indicator = indicator;
    }

    // 添加时间类型字段（MYSQL_TIME）
    void AddTime(MYSQL_TIME *time) {
        MYSQL_BIND &bind = bind_[bindIndex_++];
        bind.buffer_type = MYSQL_TYPE_DATETIME;
        bind.buffer = time;
    }

    /**
     * 绑定参数到MySQL预处理语句中
     *
     * 此函数用于将程序中的变量与MySQL预处理语句中的占位符绑定
     * 这样做可以在执行预处理语句时使用这些变量的值，而无需在每次执行时手动设置它们
     *
     * @return my_bool 类型，表示操作的成功与否 true表示成功，false表示失败
     */
    my_bool BindParameters() { return mysql_stmt_bind_param(stmt_, bind_); }

    /**
     * 绑定结果集到预处理语句
     *
     * 此函数用于将MySQL预处理语句的结果集绑定到缓冲区，以便能够读取查询结果
     * 它使用mysql_stmt_bind_result函数来进行绑定操作，该函数是MySQL C API的一部分
     *
     * @return my_bool 返回绑定操作的成功与否 true表示成功，false表示失败
     */
    my_bool BindResult() { return mysql_stmt_bind_result(stmt_, bind_); }
};

/**
 * @brief 数据库操作失败时，抛出一个运行时错误异常
 *
 * 当MariaDB数据库操作失败时，调用此函数抛出一个异常，异常信息包含具体的错误描述
 *
 * @param conn 数据库连接对象
 * @param errStr 错误描述字符串，用于提供更详细的错误信息
 * @param funcName 错误发生时的函数名，用于提供更详细的错误信息
 */
void PasteDB::ThrowDBError(MYSQL *conn, const char *funcName, const char *errStr) {
    // 创建一个字符串流，用于构造详细的错误信息
    std::stringstream errStream;
    // 将自定义的错误描述信息写入字符串流中
    errStream << funcName << errStr << "-> ";
    // 如果数据库连接指针存在，即当前对象已初始化数据库连接
    if (conn) {
        // 将MySQL的错误信息、SQL状态码、错误编号写入字符串流中
        errStream << mysql_error(conn) << " (" << mysql_sqlstate(conn) << ") [" << mysql_errno(conn) << "]";
    }

    // 使用字符串流中的错误信息创建并抛出运行时错误异常
    throw std::runtime_error(errStream.str());
}

/**
 * @brief 数据库预处理操作失败时，抛出一个运行时错误异常
 *
 * 当MariaDB预处理数据库操作失败时，调用此函数抛出一个异常，异常信息包含具体的错误描述
 *
 * @param stmt 预处理语句对象
 * @param errStr 错误描述字符串，用于提供更详细的错误信息
 * @param funcName 错误发生时的函数名，用于提供更详细的错误信息
 */
void PasteDB::ThrowDBError(MYSQL_STMT *stmt, const char *funcName, const char *errStr) {
    // 创建一个字符串流，用于构造详细的错误信息
    std::stringstream errStream;
    // 将自定义的错误描述信息写入字符串流中
    errStream << funcName << errStr << "-> ";
    // 如果stmt存在，进一步写入预处理错误信息
    if (stmt) {
        errStream << mysql_stmt_error(stmt) << " (" << mysql_stmt_sqlstate(stmt) << ") [" << mysql_stmt_errno(stmt) << "]";
    }

    // 使用字符串流中的错误信息创建并抛出运行时错误异常
    throw std::runtime_error(errStream.str());
}

/**
 * @brief 构造函数：负责初始化数据库连接并选择指定的数据库
 *
 * 此构造函数用于初始化数据库连接的参数，包括服务器地址、用户名、密码、数据库名和端口
 * 它为类的成员变量赋值，以便后续进行数据库连接和操作
 * @param host 数据库服务器地址
 * @param user 数据库用户名
 * @param passwd 数据库用户密码
 * @param db 要连接的数据库名
 * @param port 数据库服务器端口
 */
PasteDB::PasteDB(const ConnStruct &connStr) {
    // 创建数据库连接对象
    conn = mysql_init(NULL);
    if (!conn) {
        ThrowDBError(conn, "mysql_init()", "初始化数据库连接对象错误!");
    }

    // 连接数据库,判定是否正确连接 空指针报错
    if (!mysql_real_connect(conn, connStr.host, connStr.user, connStr.passwd, NULL, connStr.port, connStr.unix_socket, 0)) {
        ThrowDBError(conn, "mysql_real_connect()", "连接数据库错误!");
    }

    // 选择默认数据库 成功时返回零，失败时返回非零。
    if (mysql_select_db(conn, connStr.db)) {
        // 如果数据库不存在，提示用户是否创建
        std::cout << "未找到数据库[" << connStr.db << "],创建数据库请按 1 或 y ,其他键退出!" << std::endl;
        char tempChar;
        std::cin >> tempChar;
        if (tempChar != '1' && tempChar != 'y') {
            exit(0);
        }

        // 创建数据库
        std::string sql = "CREATE DATABASE IF NOT EXISTS " + std::string(connStr.db) +
                          " DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;";
        if (mysql_query(conn, sql.c_str())) {
            ThrowDBError(conn, "mysql_query()", "创建数据库失败!");
        }

        // 再次尝试选择数据库
        if (mysql_select_db(conn, connStr.db)) {
            ThrowDBError(conn, "mysql_select_db()", "选择数据库失败!");
        }

        // 从文件读取表结构并创建表
        PasteIO::TextToString(sql, "tables.sql");
        // 设置多语句执行选项
        mysql_set_server_option(conn, MYSQL_OPTION_MULTI_STATEMENTS_ON);
        // 执行创建表语句
        if (mysql_query(conn, sql.c_str())) {
            ThrowDBError(conn, "mysql_query()", "创建表失败!");
        }
        // 关闭多语句执行选项
        mysql_set_server_option(conn, MYSQL_OPTION_MULTI_STATEMENTS_OFF);

        std::cout << "成功创建数据库[" << connStr.db << "]" << std::endl;
    }
}

// PasteDB类的析构函数
PasteDB::~PasteDB() {
    // // 如果stmt不为空，则关闭MySQL预处理语句
    // if (stmt)
    //     mysql_stmt_close(stmt);
    // 如果conn不为空，则关闭MySQL连接
    if (conn) {
        mysql_close(conn);
    }
}

/**
 * @brief 在`ImageInfo`主表或`ImageInfo2`辅表中插入图像信息列表。
 *
 * 该函数通过接收一个图像信息结构体数组，向数据库中插入多个图像记录。
 *
 * @param imgInfoArr 图像信息数组，包含多个ImageStruct结构体。
 * @param isMainTable true表示插入到主表，false表示插入到辅表
 */
void PasteDB::ImageListInsert(std::vector<ImageStruct> &imgInfoArr, bool isMainTable) {
    // 定义函数名称常量，用于错误信息中
    const char *funcName = "ImageListInsert()";
    // 判断图像信息数组是否为空
    if (imgInfoArr.empty()) {
        ThrowDBError(conn, funcName, "imgInfoArr是空的");
    }
    // 定义SQL语句常量,不要拼接 SQL 字符串
    // `ID0`, `ID1`, `Int0`, `Int1`, `Int2`, `Int3`, `Int4`, `Int5`, `Int6`, `Int7`, `Str0`, `Str1`, `Time0`, `Time1`
    const char *sql = isMainTable ? "INSERT INTO `ImageInfo`  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, DEFAULT, DEFAULT)"
                                  : "INSERT INTO `ImageInfo2` VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, DEFAULT, DEFAULT)";

    // 开启事务，如果失败则抛出异常
    if (mysql_query(conn, "START TRANSACTION")) {
        ThrowDBError(conn, funcName, "开启事务失败!");
    }

    // 创建StmtGuard对象，自动管理预处理语句的生命周期
    StmtGuard guard(conn);
    // 获取预处理语句指针
    MYSQL_STMT *stmt = guard.get();

    // 准备预处理语句，如果失败则抛出异常
    if (mysql_stmt_prepare(stmt, sql, -1)) {
        ThrowDBError(stmt, funcName, "创建预处理错误!");
    }

    // 使用 BindHelper 简化绑定
    BindHelper helper(stmt);

    // ID 字段
    helper.AddString(&imgInfoArr[0].ID0, &imgInfoArr[0].indNts);
    helper.AddString(&imgInfoArr[0].ID1, &imgInfoArr[0].indNts);
    // Int32 类型字段
    helper.AddInt32(&imgInfoArr[0].Int0, &imgInfoArr[0].indNone);
    helper.AddInt32(&imgInfoArr[0].Int1, &imgInfoArr[0].indNone);
    helper.AddInt32(&imgInfoArr[0].Int2, &imgInfoArr[0].indNone);
    helper.AddInt32(&imgInfoArr[0].Int3, &imgInfoArr[0].indNone);
    helper.AddInt32(&imgInfoArr[0].Int4, &imgInfoArr[0].indNone);
    helper.AddInt32(&imgInfoArr[0].Int5, &imgInfoArr[0].indNone);
    helper.AddInt32(&imgInfoArr[0].Int6, &imgInfoArr[0].indNone);
    helper.AddInt32(&imgInfoArr[0].Int7, &imgInfoArr[0].indNone);
    // String 类型字段
    helper.AddString(&imgInfoArr[0].Str0, &imgInfoArr[0].indNts);
    helper.AddString(&imgInfoArr[0].Str1, &imgInfoArr[0].indNts);
    // helper.AddString(&imgInfoArr[0].Time0, &imgInfoArr[0].indNts);
    // helper.AddString(&imgInfoArr[0].Time1, &imgInfoArr[0].indNts);

    // 设置数组属性
    unsigned int arrSize = imgInfoArr.size();
    size_t rowSize = sizeof(struct ImageStruct);
    mysql_stmt_attr_set(stmt, STMT_ATTR_ARRAY_SIZE, &arrSize);
    mysql_stmt_attr_set(stmt, STMT_ATTR_ROW_SIZE, &rowSize);

    // 绑定参数到预处理语句，如果失败则抛出异常
    if (helper.BindParameters()) {
        ThrowDBError(stmt, funcName, "绑定参数失败!");
    }

    // 执行预处理语句，如果失败则抛出异常
    if (mysql_stmt_execute(stmt)) {
        ThrowDBError(stmt, funcName, "执行预处理错误!");
    }

    // 提交事务，如果失败则抛出异常
    if (mysql_commit(conn)) {
        ThrowDBError(conn, funcName, "提交事务失败!");
    }
}

/**
 * @brief 从`ImageInfo`主表或`ImageInfo2`辅表中查询特定类别的图像列表
 *
 * 此函数根据给定的类别ID从图像数据库中选择图像列表，并将结果存储在imgInfoOut参数中
 *
 * @param imgInfoArrOut 输出参数，包含选定图像信息的向量
 * @param categoryID 图像的类别ID，用于限制查询的范围
 * @param isMainTable true表示从主表查询，false表示从辅表查询
 */
void PasteDB::ImageListSelect(std::vector<ImageStruct> &imgInfoArrOut, int categoryID, bool isMainTable) {
    // 定义函数名称常量，用于错误信息中
    const char *funcName = "ImageListSelect()";
    // 定义SQL语句常量，不要拼接 SQL 字符串
    // `ID0`, `ID1`, `Int0`, `Int1`, `Int2`, `Int3`, `Int4`, `Int5`, `Int6`, `Int7`, `Str0`, `Str1`, `Time0`, `Time1`
    const char *sql = isMainTable ? "SELECT * FROM `ImageInfo`  WHERE `Int6` = ?      "
                                  : "SELECT * FROM `ImageInfo2` WHERE `Int6` = ?      ";

    // 创建StmtGuard对象，自动管理预处理语句的生命周期
    StmtGuard guard(conn);
    // 获取预处理语句指针
    MYSQL_STMT *stmt = guard.get();

    // 准备预处理语句，如果失败则抛出异常
    if (mysql_stmt_prepare(stmt, sql, -1)) {
        ThrowDBError(stmt, funcName, "创建预处理错误!");
    }

    // 初始化MYSQL_BIND结构体，用于绑定参数数组
    MYSQL_BIND bind;
    memset(&bind, 0, sizeof(bind));
    // 绑定 categoryID 到预处理语句的参数
    bind.buffer_type = MYSQL_TYPE_LONG;
    bind.buffer = &categoryID;
    bind.buffer_length = sizeof(categoryID);

    // 绑定参数到预处理语句，如果失败则抛出异常
    if (mysql_stmt_bind_param(stmt, &bind)) {
        ThrowDBError(stmt, funcName, "绑定参数失败!");
    }

    // 执行预处理语句，如果失败则抛出异常
    if (mysql_stmt_execute(stmt)) {
        ThrowDBError(stmt, funcName, "执行预处理错误!");
    }

    // 客户端缓冲整个结果，如果失败则抛出异常
    if (mysql_stmt_store_result(stmt)) {
        ThrowDBError(stmt, funcName, "客户端缓冲整个结果集错误!");
    }

    // 获取结果集的行数，如果行数为 0 则返回
    auto rowCount = mysql_stmt_num_rows(stmt);
    if (rowCount == 0) {
        return;
    }
    // 分配内存
    imgInfoArrOut.reserve(rowCount);

    ImageStruct imgInfo;
    // 显式初始化产品信息输出结构体
    memset(&imgInfo, 0, sizeof(imgInfo));

    // 设置指示符 指示符一定要设置正确,否则没法村数据库
    imgInfo.indNone = STMT_INDICATOR_NONE;
    imgInfo.indNts = STMT_INDICATOR_NTS;
    imgInfo.indNull = STMT_INDICATOR_NULL;

    // 使用 BindHelper 简化结果绑定
    BindHelper helper(stmt);

    // ID 字段
    helper.AddString(&imgInfo.ID0, nullptr, sizeof(imgInfo.ID0));
    helper.AddString(&imgInfo.ID1, nullptr, sizeof(imgInfo.ID1));
    // Int32 类型字段
    helper.AddInt32(&imgInfo.Int0, nullptr);
    helper.AddInt32(&imgInfo.Int1, nullptr);
    helper.AddInt32(&imgInfo.Int2, nullptr);
    helper.AddInt32(&imgInfo.Int3, nullptr);
    helper.AddInt32(&imgInfo.Int4, nullptr);
    helper.AddInt32(&imgInfo.Int5, nullptr);
    helper.AddInt32(&imgInfo.Int6, nullptr);
    helper.AddInt32(&imgInfo.Int7, nullptr);
    // String 类型字段
    helper.AddString(&imgInfo.Str0, nullptr, sizeof(imgInfo.Str0));
    helper.AddString(&imgInfo.Str1, nullptr, sizeof(imgInfo.Str1));
    helper.AddString(&imgInfo.Time0, nullptr, sizeof(imgInfo.Time0));
    helper.AddString(&imgInfo.Time1, nullptr, sizeof(imgInfo.Time1));

    // 绑定参数到预处理结果，如果失败则抛出异常
    if (helper.BindResult()) {
        ThrowDBError(stmt, funcName, "绑定结果失败!");
    }

    int status;
    // 获取查询结果
    // 0表示成功，1表示没有更多结果集，MYSQL_NO_DATA表示没有下一行数据了
    while ((status = mysql_stmt_fetch(stmt)) == 0) {

        imgInfoArrOut.emplace_back(imgInfo); // 避免拷贝构造
    }
    // 正确读取完数据status=MYSQL_NO_DATA(100)
    if (status != MYSQL_NO_DATA) {
        ThrowDBError(stmt, funcName, "读取数据失败!");
    }
}

/**
 * @brief 更新`ImageInfo`主表或`ImageInfo2`辅表中关键字信息
 *
 * @param imgInfoArr 图像信息数组，这是一个包含多个ImageStruct结构的向量，每个结构都描述了一个图像的信息
 * @param isMainTable true表示更新主表，false表示更新辅表
 */
void PasteDB::ImageListUpdateKey(std::vector<ImageStruct> &imgInfoArr, bool isMainTable) {
    // 定义函数名称常量，用于错误信息中
    const char *funcName = "ImageListUpdateKey()";
    // 判断图像信息数组是否为空
    if (imgInfoArr.empty()) {
        ThrowDBError(conn, funcName, "imgInfoArr是空的");
    }
    // 定义SQL语句常量，不要拼接 SQL 字符串
    // `ID0`, `ID1`, `Int0`, `Int1`, `Int2`, `Int3`, `Int4`, `Int5`, `Int6`, `Int7`, `Str0`, `Str1`, `Time0`, `Time1`
    const char *sql = isMainTable ? "UPDATE `ImageInfo ` SET `Int6` = ?, `Str0` = ? WHERE `ID0` = ?"
                                  : "UPDATE `ImageInfo2` SET `Int6` = ?, `Str0` = ? WHERE `ID0` = ?";

    // 开启事务，如果失败则抛出异常
    if (mysql_query(conn, "START TRANSACTION")) {
        ThrowDBError(conn, funcName, "开启事务失败!");
    }

    // 创建StmtGuard对象，自动管理预处理语句的生命周期
    StmtGuard guard(conn);
    // 获取预处理语句指针
    MYSQL_STMT *stmt = guard.get();

    // 准备预处理语句，如果失败则抛出异常
    if (mysql_stmt_prepare(stmt, sql, -1)) {
        ThrowDBError(stmt, funcName, "创建预处理错误!");
    }

    // 初始化MYSQL_BIND结构体，用于绑定参数
    MYSQL_BIND bind[2];
    memset(bind, 0, sizeof(bind));

    // 绑定Int6字段到预处理语句
    bind[0].buffer_type = MYSQL_TYPE_LONG;
    // 绑定Str0字段到预处理语句
    bind[1].buffer_type = MYSQL_TYPE_STRING;
    // 绑定ID0字段到预处理语句
    bind[2].buffer_type = MYSQL_TYPE_STRING;

    // 遍历图片信息数组
    for (auto &item : imgInfoArr) {
        // 为每个图片信息设置绑定变量的值
        bind[0].buffer = &item.Int6;
        bind[0].u.indicator = nullptr;

        bind[1].buffer = &item.Str0;
        bind[1].u.indicator = nullptr;
        bind[1].buffer_length = static_cast<unsigned long>(strlen(item.Str0));

        bind[2].buffer = &item.ID0;
        bind[2].u.indicator = nullptr;
        bind[2].buffer_length = static_cast<unsigned long>(strlen(item.ID0));

        // 绑定参数到预处理语句，如果失败则抛出异常
        if (mysql_stmt_bind_param(stmt, bind)) {
            ThrowDBError(stmt, funcName, "绑定参数失败!");
        }
        // 执行预处理语句，如果失败则抛出异常
        if (mysql_stmt_execute(stmt)) {
            ThrowDBError(stmt, funcName, "执行预处理错误!");
        }
        // // 重置预处理语句，以便下一个循环执行
        // if (mysql_stmt_reset(stmt)) {
        //     ThrowDBError(stmt, funcName, "重置预处理错误!");
        // }
    }
    // 提交事务，如果失败则抛出异常
    if (mysql_commit(conn)) {
        ThrowDBError(conn, funcName, "提交事务失败!");
    }
}

/**
 * @brief 从`ProductInfo`主表或`ProductInfo2`辅表中删除指定的产品信息。
 *
 * @param productNum 要删除的产品编号。
 * @param isMainTable true表示插入到主表，false表示插入到辅表
 */
void PasteDB::ProductDelete(int productNum, bool isMainTable) {
    // 定义函数名称常量，用于错误信息中
    const char *funcName = "ProductDelete()";
    // 定义SQL语句常量，不要拼接 SQL 字符串
    const char *sql = isMainTable ? "DELETE FROM `ProductInfo`  WHERE `Int0` = ?      "
                                  : "DELETE FROM `ProductInfo2` WHERE `Int0` = ?      ";

    // 创建StmtGuard对象，自动管理预处理语句的生命周期
    StmtGuard guard(conn);
    // 获取预处理语句指针
    MYSQL_STMT *stmt = guard.get();

    // 准备预处理语句，如果失败则抛出异常
    if (mysql_stmt_prepare(stmt, sql, -1)) {
        ThrowDBError(stmt, funcName, "创建预处理错误!");
    }

    // 初始化MYSQL_BIND结构体，用于绑定参数
    MYSQL_BIND bind;
    memset(&bind, 0, sizeof(bind));

    // 设置绑定参数的类型和地址
    bind.buffer_type = MYSQL_TYPE_LONG;
    bind.buffer = &productNum;
    bind.buffer_length = sizeof(productNum);

    // 绑定参数到预处理语句，如果失败则抛出异常
    if (mysql_stmt_bind_param(stmt, &bind)) {
        ThrowDBError(stmt, funcName, "绑定参数失败!");
    }

    // 执行预处理语句，如果失败则抛出异常
    if (mysql_stmt_execute(stmt)) {
        ThrowDBError(stmt, funcName, "执行预处理错误!");
    }
}

/**
 * @brief 在`ProductInfo`主表或`ProductInfo2`辅表中插入产品信息
 *
 * 此函数用于向数据库中插入一个产品的信息，根据isMainTable参数决定是插入到主表还是辅表
 *
 * @param productInfo 产品信息
 * @param isMainTable true表示插入到主表，false表示插入到辅表
 */
void PasteDB::ProductInsert(ProductStruct &productInfo, bool isMainTable) {
    // 定义函数名称常量，用于错误信息中
    const char *funcName = "ProductInsert()";
    // 定义SQL语句常量，不要拼接 SQL 字符串
    // `ID0`, `ID1`, `ID2`, `ID3`, `Int0`, `Int1`, `Int2`, `Int3`, `Int4`, `Int5`, `Int6`, `Int7`, `Big0`, `Big1`,
    // `Str0`, `Str1`, `Str2`, `Str3`, `Str4`, `Str5`, `Str6`, `Str7`, `Time0`, `Time1`
    const char *sql = isMainTable ? "INSERT INTO `ProductInfo` VALUES "
                                    "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, DEFAULT)"
                                  : "INSERT INTO `ProductInfo2` VALUES "
                                    "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, DEFAULT)";

    // 创建StmtGuard对象，自动管理预处理语句的生命周期
    StmtGuard guard(conn);
    // 获取预处理语句指针
    MYSQL_STMT *stmt = guard.get();

    // 准备预处理语句，如果失败则抛出异常
    if (mysql_stmt_prepare(stmt, sql, -1)) {
        ThrowDBError(stmt, funcName, "创建预处理错误!");
    }

    // 使用 BindHelper 简化绑定
    BindHelper helper(stmt);

    // ID 字段
    helper.AddString(&productInfo.ID0, &productInfo.indNts);
    helper.AddString(&productInfo.ID1, &productInfo.indNts);
    helper.AddString(&productInfo.ID2, &productInfo.indNts);
    helper.AddString(&productInfo.ID3, &productInfo.indNts);
    // Int32 类型字段
    helper.AddInt32(&productInfo.Int0, &productInfo.indNone);
    helper.AddInt32(&productInfo.Int1, &productInfo.indNone);
    helper.AddInt32(&productInfo.Int2, &productInfo.indNone);
    helper.AddInt32(&productInfo.Int3, &productInfo.indNone);
    helper.AddInt32(&productInfo.Int4, &productInfo.indNone);
    helper.AddInt32(&productInfo.Int5, &productInfo.indNone);
    helper.AddInt32(&productInfo.Int6, &productInfo.indNone);
    helper.AddInt32(&productInfo.Int7, &productInfo.indNone);
    // Int64 类型字段
    helper.AddInt64(&productInfo.Big0, &productInfo.indNone);
    helper.AddInt64(&productInfo.Big1, &productInfo.indNone);
    // String 类型字段
    helper.AddString(&productInfo.Str0, &productInfo.indNts);
    helper.AddString(&productInfo.Str1, &productInfo.indNts);
    helper.AddString(&productInfo.Str2, &productInfo.indNts);
    helper.AddString(&productInfo.Str3, &productInfo.indNts);
    helper.AddString(&productInfo.Str4, &productInfo.indNts);
    helper.AddString(&productInfo.Str5, &productInfo.indNts);
    helper.AddString(&productInfo.Str6, &productInfo.indNts);
    helper.AddString(&productInfo.Str7, &productInfo.indNts);
    helper.AddString(&productInfo.Time0, &productInfo.indNts);

    // 绑定参数到预处理语句，如果失败则抛出异常
    if (helper.BindParameters()) {
        ThrowDBError(stmt, funcName, "绑定参数失败!");
    }

    // 执行预处理语句，如果失败则抛出异常
    if (mysql_stmt_execute(stmt)) {
        ThrowDBError(stmt, funcName, "执行预处理错误!");
    }
}

/**
 * @brief 在`ProductInfo`主表或`ProductInfo2`辅表中批量插入产品信息
 *
 * 此函数负责将一批产品信息插入到数据库中，根据isMainTable参数决定是插入到主表还是辅表
 *
 * @param productInfoArr 产品信息数组，包含多个ProductStruct结构体。
 * @param isMainTable true表示插入到主表，false表示插入到辅表
 */
void PasteDB::ProductListInsert(std::vector<ProductStruct> &productInfoArr, bool isMainTable) {
    // 定义函数名称常量，用于错误信息中
    const char *funcName = "ProductListInsert()";
    // 判断产品信息数组是否为空
    if (productInfoArr.empty()) {
        ThrowDBError(conn, funcName, "productInfoArr");
    }
    // 定义SQL语句常量，不要拼接 SQL 字符串
    // `ID0`, `ID1`, `ID2`, `ID3`, `Int0`, `Int1`, `Int2`, `Int3`, `Int4`, `Int5`, `Int6`, `Int7`, `Big0`, `Big1`,
    // `Str0`, `Str1`, `Str2`, `Str3`, `Str4`, `Str5`, `Str6`, `Str7`, `Time0`, `Time1`
    const char *sql = isMainTable ? "INSERT INTO `ProductInfo` VALUES "
                                    "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, DEFAULT)"
                                  : "INSERT INTO `ProductInfo2` VALUES "
                                    "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, DEFAULT)";

    // 开启事务，如果失败则抛出异常
    if (mysql_query(conn, "START TRANSACTION")) {
        ThrowDBError(conn, funcName, "开启事务失败!");
    }

    // 创建StmtGuard对象，自动管理预处理语句的生命周期
    StmtGuard guard(conn);
    // 获取预处理语句指针
    MYSQL_STMT *stmt = guard.get();

    // 准备预处理语句，如果失败则抛出异常
    if (mysql_stmt_prepare(stmt, sql, -1)) {
        ThrowDBError(stmt, funcName, "创建预处理错误!");
    }

    // 使用 BindHelper 简化绑定
    BindHelper helper(stmt);

    // ID 字段
    helper.AddString(&productInfoArr[0].ID0, &productInfoArr[0].indNts);
    helper.AddString(&productInfoArr[0].ID1, &productInfoArr[0].indNts);
    helper.AddString(&productInfoArr[0].ID2, &productInfoArr[0].indNts);
    helper.AddString(&productInfoArr[0].ID3, &productInfoArr[0].indNts);
    // Int32 类型字段
    helper.AddInt32(&productInfoArr[0].Int0, &productInfoArr[0].indNone);
    helper.AddInt32(&productInfoArr[0].Int1, &productInfoArr[0].indNone);
    helper.AddInt32(&productInfoArr[0].Int2, &productInfoArr[0].indNone);
    helper.AddInt32(&productInfoArr[0].Int3, &productInfoArr[0].indNone);
    helper.AddInt32(&productInfoArr[0].Int4, &productInfoArr[0].indNone);
    helper.AddInt32(&productInfoArr[0].Int5, &productInfoArr[0].indNone);
    helper.AddInt32(&productInfoArr[0].Int6, &productInfoArr[0].indNone);
    helper.AddInt32(&productInfoArr[0].Int7, &productInfoArr[0].indNone);
    // Int64 类型字段
    helper.AddInt64(&productInfoArr[0].Big0, &productInfoArr[0].indNone);
    helper.AddInt64(&productInfoArr[0].Big1, &productInfoArr[0].indNone);
    // String 类型字段
    helper.AddString(&productInfoArr[0].Str0, &productInfoArr[0].indNts);
    helper.AddString(&productInfoArr[0].Str1, &productInfoArr[0].indNts);
    helper.AddString(&productInfoArr[0].Str2, &productInfoArr[0].indNts);
    helper.AddString(&productInfoArr[0].Str3, &productInfoArr[0].indNts);
    helper.AddString(&productInfoArr[0].Str4, &productInfoArr[0].indNts);
    helper.AddString(&productInfoArr[0].Str5, &productInfoArr[0].indNts);
    helper.AddString(&productInfoArr[0].Str6, &productInfoArr[0].indNts);
    helper.AddString(&productInfoArr[0].Str7, &productInfoArr[0].indNts);
    helper.AddString(&productInfoArr[0].Time0, &productInfoArr[0].indNts);

    // 设置数组属性
    unsigned int arrSize = productInfoArr.size();
    size_t rowSize = sizeof(struct ProductStruct);
    mysql_stmt_attr_set(stmt, STMT_ATTR_ARRAY_SIZE, &arrSize);
    mysql_stmt_attr_set(stmt, STMT_ATTR_ROW_SIZE, &rowSize);

    // 绑定参数到预处理语句，如果失败则抛出异常
    if (helper.BindParameters()) {
        ThrowDBError(stmt, funcName, "绑定参数失败!");
    }

    // 执行预处理语句，如果失败则抛出异常
    if (mysql_stmt_execute(stmt)) {
        ThrowDBError(stmt, funcName, "执行预处理错误!");
    }

    // 提交事务，如果失败则抛出异常
    if (mysql_commit(conn)) {
        ThrowDBError(conn, funcName, "提交事务失败!");
    }
}

/**
 * @brief 从`ProductInfo`主表或`ProductInfo2`辅表中查询产品信息
 *
 * 此函数根据提供的参数从数据库中查询相应的产品信息，并通过引用参数productInfoOut返回
 * 它允许用户根据需要从主表或辅表中查询产品，并可以根据特定类别进行筛选
 *
 * @param productInfoOut 用于存储从数据库中获取的产品信息的结构体引用
 * @param productNum 需要查询的产品编号
 * @param categoryID 产品的类别ID，用于限制查询的范围
 * @param isMainTable true表示从主表查询，false表示从辅表查询
 */
void PasteDB::ProductSelect(ProductStruct &productInfoOut, int productNum, int categoryID, bool isMainTable) {
    // 定义函数名称常量，用于错误信息中
    const char *funcName = "ProductSelect()";
    // 定义SQL语句常量，不要拼接 SQL 字符串
    const char *sql = isMainTable ? ((categoryID > -1) ? "SELECT * FROM `ProductInfo` WHERE `Int0` = ? AND `Int6` = ? LIMIT 1"
                                                       : "SELECT * FROM `ProductInfo` WHERE `Int0` = ? LIMIT 1")
                                  : ((categoryID > -1) ? "SELECT * FROM `ProductInfo2` WHERE `Int0` = ? AND `Int6` = ? LIMIT 1"
                                                       : "SELECT * FROM `ProductInfo2` WHERE `Int0` = ? LIMIT 1");

    // 创建StmtGuard对象，自动管理预处理语句的生命周期
    StmtGuard guard(conn);
    // 获取预处理语句指针
    MYSQL_STMT *stmt = guard.get();

    // 准备预处理语句，如果失败则抛出异常
    if (mysql_stmt_prepare(stmt, sql, -1)) {
        ThrowDBError(stmt, funcName, "创建预处理错误!");
    }

    // 初始化MYSQL_BIND结构体，用于绑定参数数组
    MYSQL_BIND bind[2];
    memset(bind, 0, sizeof(bind));

    // 绑定 productID 参数
    bind[0].buffer_type = MYSQL_TYPE_LONG;
    bind[0].buffer = &productNum;
    bind[0].buffer_length = sizeof(productNum);

    // 如果 categoryID 大于 -1，则绑定 categoryID 参数
    if (categoryID > -1) {
        bind[1].buffer_type = MYSQL_TYPE_LONG;
        bind[1].buffer = &categoryID;
        bind[1].buffer_length = sizeof(categoryID);
    }

    // 绑定参数到预处理语句，如果失败则抛出异常
    if (mysql_stmt_bind_param(stmt, bind)) {
        ThrowDBError(stmt, funcName, "绑定参数失败!");
    }

    // 执行预处理语句，如果失败则抛出异常
    if (mysql_stmt_execute(stmt)) {
        ThrowDBError(stmt, funcName, "执行预处理错误!");
    }

    // 显式初始化产品信息输出结构体
    memset(&productInfoOut, 0, sizeof(productInfoOut));
    // 设置指示符 指示符一定要设置正确,否则没法村数据库
    productInfoOut.indNone = STMT_INDICATOR_NONE;
    productInfoOut.indNts = STMT_INDICATOR_NTS;
    productInfoOut.indNull = STMT_INDICATOR_NULL;

    // 使用 BindHelper 简化结果绑定
    BindHelper helper(stmt);

    // ID 字段
    helper.AddString(&productInfoOut.ID0, nullptr, sizeof(productInfoOut.ID0));
    helper.AddString(&productInfoOut.ID1, nullptr, sizeof(productInfoOut.ID1));
    helper.AddString(&productInfoOut.ID2, nullptr, sizeof(productInfoOut.ID2));
    helper.AddString(&productInfoOut.ID3, nullptr, sizeof(productInfoOut.ID3));
    // Int32 类型字段
    helper.AddInt32(&productInfoOut.Int0, nullptr);
    helper.AddInt32(&productInfoOut.Int1, nullptr);
    helper.AddInt32(&productInfoOut.Int2, nullptr);
    helper.AddInt32(&productInfoOut.Int3, nullptr);
    helper.AddInt32(&productInfoOut.Int4, nullptr);
    helper.AddInt32(&productInfoOut.Int5, nullptr);
    helper.AddInt32(&productInfoOut.Int6, nullptr);
    helper.AddInt32(&productInfoOut.Int7, nullptr);
    // Int64 类型字段
    helper.AddInt64(&productInfoOut.Big0, nullptr);
    helper.AddInt64(&productInfoOut.Big1, nullptr);
    // String 类型字段
    helper.AddString(&productInfoOut.Str0, nullptr, sizeof(productInfoOut.Str0));
    helper.AddString(&productInfoOut.Str1, nullptr, sizeof(productInfoOut.Str1));
    helper.AddString(&productInfoOut.Str2, nullptr, sizeof(productInfoOut.Str2));
    helper.AddString(&productInfoOut.Str3, nullptr, sizeof(productInfoOut.Str3));
    helper.AddString(&productInfoOut.Str4, nullptr, sizeof(productInfoOut.Str4));
    helper.AddString(&productInfoOut.Str5, nullptr, sizeof(productInfoOut.Str5));
    helper.AddString(&productInfoOut.Str6, nullptr, sizeof(productInfoOut.Str6));
    helper.AddString(&productInfoOut.Str7, nullptr, sizeof(productInfoOut.Str7));
    helper.AddString(&productInfoOut.Time0, nullptr, sizeof(productInfoOut.Time0));
    helper.AddString(&productInfoOut.Time1, nullptr, sizeof(productInfoOut.Time1));

    // 绑定参数到预处理结果，如果失败则抛出异常
    if (helper.BindResult()) {
        ThrowDBError(stmt, funcName, "绑定结果失败!");
    }

    // 获取查询结果
    mysql_stmt_fetch(stmt);
}

/**
 * @brief 往`ProductInfo`主表或`ProductInfo2`辅表中图片ID字段中插入图片ID
 *
 * 该函数用于更新指定产品的图片ID
 *
 * @param productNum 产品编号，用于标识需要更新图片ID的产品
 * @param imageID 指向图片ID的字符数组(去吃'-'的UUID)，表示要更新的图片ID
 * @param isMainTable true表示更新主表，false表示更新辅表
 */
// void PasteDB::ProductUpdateImageID(int productNum, char *imageID, bool isMainTable) {
//     // 定义函数名称常量，用于错误信息中
//     const char *funcName = "ProductUpdateImageID()";
//     // 定义SQL语句常量，不要拼接 SQL 字符串
//     const char *sql = isMainTable ? "UPDATE `ProductInfo`  SET `ID3` = ? WHERE `Int0` = ?"
//                                   : "UPDATE `ProductInfo2` SET `ID3` = ? WHERE `Int0` = ?";

//     // 创建StmtGuard对象，自动管理预处理语句的生命周期
//     StmtGuard guard(conn);
//     // 获取预处理语句指针
//     MYSQL_STMT *stmt = guard.get();

//     // 准备预处理语句，如果失败则抛出异常
//     if (mysql_stmt_prepare(stmt, sql, -1)) {
//         ThrowDBError(stmt, funcName, "创建预处理错误!");
//     }

//     // 初始化MYSQL_BIND结构体，用于绑定参数
//     MYSQL_BIND bind[2] = {};

//     // 设置绑定参数的类型和地址
//     char indNts = STMT_INDICATOR_NTS;
//     bind[0].buffer_type = MYSQL_TYPE_STRING;
//     bind[0].buffer = imageID;
//     bind[0].u.indicator = &indNts;
//     bind[0].buffer_length = strlen(imageID);
//     bind[0].length = &bind[0].buffer_length;

//     char indNone = STMT_INDICATOR_NONE;
//     bind[1].buffer_type = MYSQL_TYPE_LONG;
//     bind[1].buffer = &productNum;
//     bind[1].u.indicator = &indNone;
//     // 绑定参数到预处理语句，如果失败则抛出异常
//     if (mysql_stmt_bind_param(stmt, bind)) {
//         ThrowDBError(stmt, funcName, "绑定参数失败!");
//     }

//     // 执行预处理语句，如果失败则抛出异常
//     if (mysql_stmt_execute(stmt)) {
//         ThrowDBError(stmt, funcName, "执行预处理错误!");
//     }
// }

/**
 * 更新产品信息到数据库
 *
 * 根据`Int0`
 * 更新了`ID3`, `Int1`, `Int2`, `Int4`, `Int6`, `Str4`, `Str5`, `Str6`, `Str7` 共9个字段
 *
 * @param productInfo 一个引用参数，包含需要更新的产品信息
 * @param isMainTable true表示更新主表，false表示更新辅表
 */
void PasteDB::ProductUpdate(ProductStruct &productInfo, bool isMainTable) {
    // 定义函数名称常量，用于错误信息中
    const char *funcName = "ProductInsert()";
    // 定义SQL语句常量，不要拼接 SQL 字符串
    // `ID0`, `ID1`, `ID2`, `ID3`, `Int0`, `Int1`, `Int2`, `Int3`, `Int4`, `Int5`, `Int6`, `Int7`, `Big0`, `Big1`,
    // `Str0`, `Str1`, `Str2`, `Str3`, `Str4`, `Str5`, `Str6`, `Str7`, `Time0`, `Time1`
    const char *sql = isMainTable ? "UPDATE `ProductInfo`  SET `ID3` = ?, `Int1` = ?, `Int2` = ?, `Int4` = ?, `Int6` = ?, "
                                    "`Str4` = ?, `Str5` = ?, `Str6` = ?, `Str7` = ? WHERE `Int0` = ?"
                                  : "UPDATE `ProductInfo2` SET `ID3` = ?, `Int1` = ?, `Int2` = ?, `Int4` = ?, `Int6` = ?, "
                                    "`Str4` = ?, `Str5` = ?, `Str6` = ?, `Str7` = ? WHERE `Int0` = ?";

    // 创建StmtGuard对象，自动管理预处理语句的生命周期
    StmtGuard guard(conn);
    // 获取预处理语句指针
    MYSQL_STMT *stmt = guard.get();

    // 准备预处理语句，如果失败则抛出异常
    if (mysql_stmt_prepare(stmt, sql, -1)) {
        ThrowDBError(stmt, funcName, "创建预处理错误!");
    }

    // 使用 BindHelper 简化绑定
    BindHelper helper(stmt);

    // ID 字段
    helper.AddString(&productInfo.ID3, &productInfo.indNts);
    // Int32 类型字段
    helper.AddInt32(&productInfo.Int1, &productInfo.indNone);
    helper.AddInt32(&productInfo.Int2, &productInfo.indNone);
    helper.AddInt32(&productInfo.Int4, &productInfo.indNone);
    helper.AddInt32(&productInfo.Int6, &productInfo.indNone);
    // String 类型字段
    helper.AddString(&productInfo.Str4, &productInfo.indNts);
    helper.AddString(&productInfo.Str5, &productInfo.indNts);
    helper.AddString(&productInfo.Str6, &productInfo.indNts);
    helper.AddString(&productInfo.Str7, &productInfo.indNts);

    // 查询条件
    helper.AddInt32(&productInfo.Int0, &productInfo.indNone);
    // 绑定参数到预处理语句，如果失败则抛出异常
    if (helper.BindParameters()) {
        ThrowDBError(stmt, funcName, "绑定参数失败!");
    }

    // 执行预处理语句，如果失败则抛出异常
    if (mysql_stmt_execute(stmt)) {
        ThrowDBError(stmt, funcName, "执行预处理错误!");
    }
}

/**
 * @brief 从`ProductInfo2`辅表中查询产品信息列表
 *
 * 辅表中产品编号会有重复，所以需要根据产品编号查询信息列表
 *
 * @param productInfoArrOut 用于存储从数据库中获取的产品信息列表的结构体引用
 * @param productNum 需要查询的产品编号
 */
void PasteDB::Product2ListSelect(std::vector<ProductStruct> &productInfoArrOut, int productNum) {
    // 定义函数名称常量，用于错误信息中
    const char *funcName = "Product2ListSelect()";
    // 定义SQL语句常量，不要拼接 SQL 字符串
    const char *sql = "SELECT * FROM `ProductInfo2` WHERE `Int0` = ? ";

    // 创建StmtGuard对象，自动管理预处理语句的生命周期
    StmtGuard guard(conn);
    // 获取预处理语句指针
    MYSQL_STMT *stmt = guard.get();

    // 准备预处理语句，如果失败则抛出异常
    if (mysql_stmt_prepare(stmt, sql, -1)) {
        ThrowDBError(stmt, funcName, "创建预处理错误!");
    }

    // 初始化MYSQL_BIND结构体，用于绑定参数数组
    MYSQL_BIND bind;
    memset(&bind, 0, sizeof(bind));

    // 绑定 productID 参数
    bind.buffer_type = MYSQL_TYPE_LONG;
    bind.buffer = &productNum;
    bind.buffer_length = sizeof(productNum);

    // 绑定参数到预处理语句，如果失败则抛出异常
    if (mysql_stmt_bind_param(stmt, &bind)) {
        ThrowDBError(stmt, funcName, "绑定参数失败!");
    }

    // 执行预处理语句，如果失败则抛出异常
    if (mysql_stmt_execute(stmt)) {
        ThrowDBError(stmt, funcName, "执行预处理错误!");
    }

    // 客户端缓冲整个结果，如果失败则抛出异常
    if (mysql_stmt_store_result(stmt)) {
        ThrowDBError(stmt, funcName, "客户端缓冲整个结果集错误!");
    }

    // 获取结果集的行数，如果行数为 0 则返回
    auto rowCount = mysql_stmt_num_rows(stmt);
    if (rowCount == 0) {
        return;
    }
    // 分配内存
    productInfoArrOut.reserve(rowCount);

    ProductStruct productInfo;
    // 显式初始化产品信息输出结构体
    memset(&productInfo, 0, sizeof(productInfo));
    // 设置指示符 指示符一定要设置正确,否则没法村数据库
    productInfo.indNone = STMT_INDICATOR_NONE;
    productInfo.indNts = STMT_INDICATOR_NTS;
    productInfo.indNull = STMT_INDICATOR_NULL;

    // 使用 BindHelper 简化结果绑定
    BindHelper helper(stmt);

    // ID 字段
    helper.AddString(&productInfo.ID0, nullptr, sizeof(productInfo.ID0));
    helper.AddString(&productInfo.ID1, nullptr, sizeof(productInfo.ID1));
    helper.AddString(&productInfo.ID2, nullptr, sizeof(productInfo.ID2));
    helper.AddString(&productInfo.ID3, nullptr, sizeof(productInfo.ID3));
    // Int32 类型字段
    helper.AddInt32(&productInfo.Int0, nullptr);
    helper.AddInt32(&productInfo.Int1, nullptr);
    helper.AddInt32(&productInfo.Int2, nullptr);
    helper.AddInt32(&productInfo.Int3, nullptr);
    helper.AddInt32(&productInfo.Int4, nullptr);
    helper.AddInt32(&productInfo.Int5, nullptr);
    helper.AddInt32(&productInfo.Int6, nullptr);
    helper.AddInt32(&productInfo.Int7, nullptr);
    // Int64 类型字段
    helper.AddInt64(&productInfo.Big0, nullptr);
    helper.AddInt64(&productInfo.Big1, nullptr);
    // String 类型字段
    helper.AddString(&productInfo.Str0, nullptr, sizeof(productInfo.Str0));
    helper.AddString(&productInfo.Str1, nullptr, sizeof(productInfo.Str1));
    helper.AddString(&productInfo.Str2, nullptr, sizeof(productInfo.Str2));
    helper.AddString(&productInfo.Str3, nullptr, sizeof(productInfo.Str3));
    helper.AddString(&productInfo.Str4, nullptr, sizeof(productInfo.Str4));
    helper.AddString(&productInfo.Str5, nullptr, sizeof(productInfo.Str5));
    helper.AddString(&productInfo.Str6, nullptr, sizeof(productInfo.Str6));
    helper.AddString(&productInfo.Str7, nullptr, sizeof(productInfo.Str7));
    helper.AddString(&productInfo.Time0, nullptr, sizeof(productInfo.Time0));
    helper.AddString(&productInfo.Time1, nullptr, sizeof(productInfo.Time1));

    // 绑定参数到预处理结果，如果失败则抛出异常
    if (helper.BindResult()) {
        ThrowDBError(stmt, funcName, "绑定结果失败!");
    }

    int status;
    // 获取查询结果
    // 0表示成功，1表示没有更多结果集，MYSQL_NO_DATA表示没有下一行数据了
    while ((status = mysql_stmt_fetch(stmt)) == 0) {

        productInfoArrOut.emplace_back(productInfo); // 避免拷贝构造
    }
    // 正确读取完数据status=MYSQL_NO_DATA(100)
    if (status != MYSQL_NO_DATA) {

        ThrowDBError(stmt, funcName, "读取数据失败!");
    }
}
